package com.yugao.fintech.draper.security.handler;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.text.StrPool;
import com.yugao.fintech.draper.auth.api.SocialFeignApi;
import com.yugao.fintech.draper.auth.dto.SocialUserDTO;
import com.yugao.fintech.draper.auth.dto.UserInfoVO;
import com.yugao.fintech.draper.core.model.ErrorCode;
import com.yugao.fintech.draper.security.authentication.social.GiteeTokenResponse;
import com.yugao.fintech.draper.security.authentication.social.GiteeUserInfoResponse;
import com.yugao.fintech.draper.security.constants.GiteeEndpointEnums;
import com.yugao.fintech.draper.security.exception.AppidGiteeException;
import com.yugao.fintech.draper.security.exception.RedirectUriGiteeException;
import com.yugao.fintech.draper.security.config.properties.GiteeProperties;
import com.yugao.fintech.draper.core.exception.BaseException;
import com.yugao.fintech.draper.security.SecurityUser;
import com.yugao.fintech.draper.security.util.ArtOAuth2EndpointUtils;
import com.yugao.fintech.draper.core.util.ResultConsumer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
public class DefaultGiteeRegisteredClientRepository implements GiteeRegisteredClientRepository {

    private final GiteeProperties giteeProperties;

    private final StringRedisTemplate redisTemplate;

    private final SocialFeignApi socialFeignApi;

    private final static String GITEE_STATE_PREFIX = "gitee_state_prefix";

    private final static String GITEE_USERS_PREFIX = "gitee_users_prefix";

    /**
     * @param appid 码云配置的客户端Id
     * @return 重定向地址
     */
    @Override
    public String getRedirectUriByAppid(String appid) {
        Assert.hasText(appid, "appid不能为空！");
        GiteeProperties.Gitee gitee = getGiteeByAppid(appid);
        String redirectUriPrefix = gitee.getRedirectUriPrefix();
        if (StringUtils.hasText(redirectUriPrefix)) {
            return UriUtils.encode(redirectUriPrefix + "/" + appid, StandardCharsets.UTF_8);
        } else {
            OAuth2Error error = new OAuth2Error(ErrorCode.SYSTEM_ERROR.getCode().toString(), "重定向地址前缀不能为空", null);
            throw new RedirectUriGiteeException(error);
        }
    }

    /**
     * 生成状态码
     *
     * @param appid 开放平台 网站应用 ID
     * @return 状态码
     */
    @Override
    public String stateGenerate(String appid) {
        String state = UUID.randomUUID().toString();
        redisTemplate.opsForValue()
                .set(GITEE_STATE_PREFIX + StrPool.COLON + appid + StrPool.COLON + state, state, 30, TimeUnit.MINUTES);
        return state;
    }

    /**
     * @param appid  开放平台 网站应用 ID
     * @param id     码云用户唯一标识
     * @param userId 系统用户id
     */
    @Override
    public void storeBinding(Integer id, String appid, Long userId) {
        SocialUserDTO socialUserDTO = new SocialUserDTO();
        socialUserDTO.setAppid(appid);
        socialUserDTO.setId(id);
        socialUserDTO.setUsersId(userId);
        socialFeignApi.binding(socialUserDTO);
    }

    /**
     * 储存gitee用户
     *
     * @param giteeTokenResponse gitee token响应
     * @param userInfo           gitee 用户信息响应
     * @param appid              码云网站应用ID
     */
    @Override
    public void storeGiteeUsers(GiteeTokenResponse giteeTokenResponse, GiteeUserInfoResponse userInfo, String appid) {
        String accessToken = giteeTokenResponse.getAccessToken();
        String refreshToken = giteeTokenResponse.getRefreshToken();
        String scope = giteeTokenResponse.getScope();
        Integer expiresIn = giteeTokenResponse.getExpiresIn();
        LocalDateTime expires = LocalDateTime.now().plusSeconds(expiresIn);
        Integer id = userInfo.getId();
        String avatarUrl = userInfo.getAvatarUrl();

        SocialUserDTO usersGitee = ResultConsumer.ofNullable(socialFeignApi.getByAppidAndId(appid, id))
                .assertSuccess(r -> new BaseException("查询接口失败"))
                .peek(d -> log.info("usersGitee:{}", d))
                .getData();

        if (Objects.isNull(usersGitee)) {
            SocialUserDTO gitee = new SocialUserDTO();
            BeanUtils.copyProperties(userInfo, gitee);
            gitee.setAppid(appid);
            gitee.setAccessToken(accessToken);
            gitee.setRefreshToken(refreshToken);
            gitee.setExpires(expires);
            gitee.setScope(scope);
            socialFeignApi.add(gitee);
        } else {
            usersGitee.setAccessToken(accessToken);
            usersGitee.setRefreshToken(refreshToken);
            usersGitee.setExpires(expires);
            usersGitee.setScope(scope);
            usersGitee.setAvatarUrl(avatarUrl);
            socialFeignApi.update(usersGitee);
        }
    }

    /**
     * 检验状态码
     *
     * @param appid 码云应用 ID
     * @param code  授权码
     * @param state 状态码
     * @return true or false
     */
    @Override
    public boolean stateValid(String appid, String code, String state) {
        Assert.hasText(state, "状态码不能为空！");
        Assert.hasText(code, "授权码不能为空！");

        String cacheState = redisTemplate.opsForValue()
                .get(GITEE_STATE_PREFIX + StrPool.COLON + appid + StrPool.COLON + state);
        Assert.hasText(cacheState, "状态码校验失效！");

        assert cacheState != null;
        return cacheState.equals(state);
    }

    /**
     * @param appid 码云配置的客户端id
     * @return GiteeProperties.Gitee
     */
    @Override
    public GiteeProperties.Gitee getGiteeByAppid(String appid) throws OAuth2AuthenticationException {
        Assert.hasText(appid, "appid不能为空！");

        List<GiteeProperties.Gitee> clients = giteeProperties.getList();
        if (CollectionUtil.isEmpty(clients)) {
            return null;
        }

        Optional<GiteeProperties.Gitee> optional = clients.stream().filter(c -> appid.equals(c.getAppid())).findFirst();
        if (optional.isPresent()) {
            return optional.get();
        } else {
            OAuth2Error error = new OAuth2Error(ErrorCode.SYSTEM_ERROR.getCode().toString(), "为配置的appid", null);
            throw new AppidGiteeException(error);
        }
    }

    /**
     * @param appid          AppID(码云Gitee
     *                       client_id)，<a href="https://gitee.com/api/v5/oauth_doc">OAuth文档</a>
     * @param code           授权码，<a href="https://gitee.com/api/v5/oauth_doc">OAuth文档</a>
     * @param accessTokenUrl <a href="https://gitee.com/api/v5/oauth_doc">OAuth文档</a>
     * @return
     * @throws OAuth2AuthenticationException
     */
    @Override
    public GiteeTokenResponse getAccessTokenResponse(String appid, String code, String accessTokenUrl)
            throws OAuth2AuthenticationException {
        GiteeProperties.Gitee gitee = getGiteeByAppid(appid);
        Map<String, String> uriVariables = new HashMap<>(8);

        uriVariables.put(OAuth2ParameterNames.CLIENT_ID, appid);
        uriVariables.put(OAuth2ParameterNames.CODE, code);
        uriVariables.put(OAuth2ParameterNames.CLIENT_SECRET, gitee.getSecret());
        uriVariables.put(OAuth2ParameterNames.REDIRECT_URI, gitee.getRedirectUriPrefix() + "/" + appid);

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);

        // 将请求体放入HttpEntity中
        HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(httpHeaders);

        GiteeTokenResponse giteeTokenResponse = null;
        try {
            giteeTokenResponse = restTemplate.postForObject(accessTokenUrl, httpEntity, GiteeTokenResponse.class,
                    uriVariables);
        } catch (Exception e) {
            ArtOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
                    ArtOAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
        }
        if (Objects.isNull(giteeTokenResponse)) {
            ArtOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
                    ArtOAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
        }

        if (giteeTokenResponse.getAccessToken() == null) {
            ArtOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
                    ArtOAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
        }

        return giteeTokenResponse;
    }

    /**
     * @param userinfoUrl        用户信息接口
     * @param appid              AppID(码云Gitee client_id)
     * @param state              状态码
     * @param giteeTokenResponse 码云 Token
     */
    @Override
    public GiteeUserInfoResponse getUserInfo(String userinfoUrl, String appid, String state,
                                             GiteeTokenResponse giteeTokenResponse) {
        RestTemplate restTemplate = new RestTemplate();
        String accessToken = giteeTokenResponse.getAccessToken();

        GiteeUserInfoResponse giteeUserInfoResponse = null;
        try {
            giteeUserInfoResponse = restTemplate.getForObject(userinfoUrl + "?access_token=" + accessToken,
                    GiteeUserInfoResponse.class);
        } catch (Exception e) {
            ArtOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "", GiteeEndpointEnums.USERINFO_URL);
        }

        if (giteeUserInfoResponse == null) {
            ArtOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "", GiteeEndpointEnums.USERINFO_URL);
        }

        Integer id = giteeUserInfoResponse.getId();
        if (id == null) {
            ArtOAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "", GiteeEndpointEnums.USERINFO_URL);
        }

        return giteeUserInfoResponse;
    }

    /**
     * 获取登录系统的用户信息
     *
     * @param appid 码云应用id
     * @param id    码云用户唯一标识
     * @return 系统用户信息
     */
    @Override
    public UserDetails getUser(String appid, Integer id) {
        UserInfoVO userDTO = ResultConsumer.ofNullable(socialFeignApi.getUser(appid, id))
                .assertSuccess(r -> new BaseException("查询接口失败"))
                .peek(u -> log.info("user:{}", u))
                .getData();

        if (Objects.isNull(userDTO)) {
            // todo 未绑定用户，将以游客角色登录
            OAuth2Error error = new OAuth2Error(ErrorCode.SYSTEM_ERROR.getCode().toString(), "用户未绑定，请先绑定用户！", null);
            throw new OAuth2AuthenticationException(error);
        }

        boolean notLocked = org.apache.commons.lang3.StringUtils.equals(UserInfoVO.STATUS_VALID, userDTO.getStatus());

        SecurityUser authUser = new SecurityUser(userDTO.getUsername(), userDTO.getPassword(), true, true, true,
                notLocked, AuthorityUtils.commaSeparatedStringToAuthorityList(userDTO.getPerms()));
        BeanUtils.copyProperties(userDTO, authUser);

        return authUser;
    }

    @Override
    public void tagUsers(String appid, String state, Long userId) {
        redisTemplate.opsForValue()
                .set(GITEE_USERS_PREFIX + StrPool.COLON + appid + StrPool.COLON + state, String.valueOf(userId), 30,
                        TimeUnit.MINUTES);
    }

    @Override
    public String getTagUser(String appid, String state) {
        return redisTemplate.opsForValue().get(GITEE_USERS_PREFIX + StrPool.COLON + appid + StrPool.COLON + state);
    }

}
